/**
 * 
 */
package org.swing.utility.common.string;

import java.io.File;
import java.math.RoundingMode;
import java.security.InvalidParameterException;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.swing.utility.common.datetime.DateParser.InvalidDateException;
import org.swing.utility.common.datetime.Iso8601Date;

/**
 * @author lqnhu
 *
 */
public class FormatUtil {
	private DateFormat dateFormat;
	private NumberFormat integerFormat;
	private NumberFormat floatFormat;

	/** Constructs a new FormatUtil and initializes various internal formatters. */
	public FormatUtil() {
		this.dateFormat = new SimpleDateFormat("yyyy-MM-dd");
		this.integerFormat = NumberFormat.getIntegerInstance();
		this.integerFormat.setGroupingUsed(false);
		this.floatFormat = NumberFormat.getNumberInstance();
		this.floatFormat.setGroupingUsed(false);
		this.floatFormat.setMaximumFractionDigits(6);
		this.floatFormat.setRoundingMode(RoundingMode.HALF_DOWN);
		if (this.floatFormat instanceof DecimalFormat) {
			final DecimalFormat decimalFormat = (DecimalFormat) this.floatFormat;
			final DecimalFormatSymbols decimalFormatSymbols = decimalFormat
					.getDecimalFormatSymbols();
			decimalFormatSymbols.setNaN("?");
			decimalFormatSymbols.setInfinity("?");
			decimalFormat.setDecimalFormatSymbols(decimalFormatSymbols);
		}
	}

	/** Formats a short to an integer string. */
	public String format(short value) {
		return this.integerFormat.format(value);
	}

	/** Formats an int to an integer string. */
	public String format(int value) {
		return this.integerFormat.format(value);
	}

	/** Formats a long to an integer string. */
	public String format(long value) {
		return this.integerFormat.format(value);
	}

	/** Formats a float to a floating point string. */
	public String format(float value) {
		return this.floatFormat.format(value);
	}

	/** Formats a double to a floating point string. */
	public String format(double value) {
		return this.floatFormat.format(value);
	}

	/** Formats an enum to the String representation of an enum. */
	public String format(Enum value) {
		return value.name();
	}

	/** Formats a date to a date string without time. */
	public String format(Date value) {
		return this.dateFormat.format(value);
	}

	/** Formats date & time */
	public String format(final Iso8601Date value) {
		return value.toString();
	}

	/** Formats a boolean value to a String. */
	public String format(boolean value) {
		if (value)
			return "Y";
		else
			return "N";
	}

	/** Attempts to determine the type of value and format it appropriately. */
	public String format(Object value) {
		if (value == null)
			return "";
		if (value instanceof Short)
			return format(((Short) value).shortValue());
		if (value instanceof Integer)
			return format(((Integer) value).intValue());
		if (value instanceof Long)
			return format(((Long) value).longValue());
		if (value instanceof Float)
			return format(((Float) value).floatValue());
		if (value instanceof Double)
			return format(((Double) value).doubleValue());
		if (value instanceof Enum)
			return format(((Enum) value));
		if (value instanceof Iso8601Date)
			return format((Iso8601Date) value);
		if (value instanceof Date)
			return format(((Date) value));
		if (value instanceof Boolean)
			return format(((Boolean) value).booleanValue());
		return value.toString();
	}

	// /////////////////////////////////////////////////////////////////////////
	// Parsing methods
	// /////////////////////////////////////////////////////////////////////////
	/** Parses a String into a short. */
	public short parseShort(String value) {
		return Short.parseShort(value);
	}

	/** Parses a String into an int. */
	public int parseInt(String value) {
		return Integer.parseInt(value);
	}

	/** Parses a String into a long. */
	public long parseLong(String value) {
		return Long.parseLong(value);
	}

	/** Parses a String into a float. */
	public float parseFloat(String value) {
		if ("?".equals(value) || "-?".equals(value))
			return Float.NaN;
		else
			return Float.parseFloat(value);
	}

	/** Parses a String into a double. */
	public double parseDouble(String value) {
		if ("?".equals(value) || "-?".equals(value))
			return Double.NaN;
		else
			return Double.parseDouble(value);
	}

	/** Parses a String into an Enum of the given type. */
	public <E extends Enum> E parseEnum(String value, Class<E> type) {
		return (E) Enum.valueOf(type, value);
	}

	/** Parses a String into a date. 
	 * @throws Exception */
	public Date parseDate(String value) throws Exception {
		try {
			return this.dateFormat.parse(value);
		} catch (ParseException pe) {
			throw new Exception("Could not parse value as date: " + pe.toString());
		}
	}

	/** Parse a String into an Iso8601 Date 
	 * @throws InvalidDateException */
	public Iso8601Date parseIso8601Date(String value) throws InvalidDateException {
		return new Iso8601Date(value);
	}

	/** Parses a String into a boolean. */
	public boolean parseBoolean(String value) {
		if (value == null || value.length() == 0)
			return false;
		char ch = Character.toUpperCase(value.charAt(0));
		return (ch == 'Y');
	}

	/**
	 * Attempts to determine the correct parse method to call based on the
	 * desired return type and then parses the String and returns the value.
	 *
	 * @param value
	 *            the String value to be parsed
	 * @param returnType
	 *            the desired return type
	 * @return an object of the returnType
	 * @throws Exception 
	 */
	public Object parseObject(String value, Class<?> returnType) throws Exception {
		if (returnType == Short.class || returnType == Short.TYPE)
			return parseShort(value);
		if (returnType == Integer.class || returnType == Integer.TYPE)
			return parseInt(value);
		if (returnType == Long.class || returnType == Long.TYPE)
			return parseLong(value);
		if (returnType == Float.class || returnType == Float.TYPE)
			return parseFloat(value);
		if (returnType == Double.class || returnType == Double.TYPE)
			return parseDouble(value);
		if (returnType == Boolean.class || returnType == Boolean.TYPE)
			return parseBoolean(value);
		if (returnType == Iso8601Date.class)
			return parseIso8601Date(value);
		if (returnType == Date.class)
			return parseDate(value);
		if (returnType == Byte.class || returnType == Byte.TYPE)
			return parseInt(value);
		if (returnType == File.class)
			return new File(value);
		if (Enum.class.isAssignableFrom(returnType))
			return parseEnum(value, (Class<? extends Enum>) returnType);
		if (returnType == String.class)
			return value;
		throw new InvalidParameterException(
				"Don't know how to convert a String to a "
						+ returnType.getName());
	}
}
